Mobile — Technical Reference
This document covers the technical details for building mobile Aspects that integrate with the Candescent Digital Banking platform. Mobile Aspects run inside a WebView managed by the native app, which introduces a fundamentally different execution model compared to web Aspects.
For the web-specific technical reference, see Web Technical Reference.
For a side-by-side comparison of how web and mobile Aspects differ, see the Web vs. Mobile table in the Aspects Overview.
HTML Wrapper Structure
Mobile Aspects are delivered as full HTML documents. The platform loads this HTML into a WebView. Unlike web Aspects (which are plain .js files), the mobile Aspect must include the HTML scaffolding.
<html>
<head></head>
<body>
<script type="text/javascript">
// Meta tag injection
var metaCharset = document.createElement('meta');
metaCharset.setAttribute('charset', 'utf-8');
document.head.appendChild(metaCharset);
var metaViewport = document.createElement('meta');
metaViewport.setAttribute('name', 'viewport');
metaViewport.setAttribute('content', 'width=device-width, initial-scale=1, shrink-to-fit=no');
document.head.appendChild(metaViewport);
// Your Aspect code goes here
</script>
</body>
</html>
Meta tags for charset and viewport should be injected programmatically at the start of the script for consistent rendering across devices.
Native Bridge Communication
The native app exposes bridge interfaces that allow the Aspect to communicate with the host app. There are two bridges, each with separate APIs for iOS and Android.
Bridge Detection Pattern
All bridge calls must handle both iOS and Android:
function sendToNativeBridge(bridgeName, payload) {
var jsonString = JSON.stringify(payload);
if (navigator.userAgent && navigator.userAgent.match(/(iPad|iPhone|iPod)/i)) {
window.webkit.messageHandlers[bridgeName].postMessage(jsonString);
} else {
JSBridge[bridgeName](jsonString);
}
}
Available Bridges
| Bridge | Purpose | iOS API | Android API |
|---|---|---|---|
tokenApiDetails | Request authentication — sends API call details to the native app for execution | window.webkit.messageHandlers.tokenApiDetails.postMessage(json) | JSBridge.tokenApiDetails(json) |
sizeAndLocation | Report layout requirements — tells the native app how to size and position the WebView | window.webkit.messageHandlers.sizeAndLocation.postMessage(json) | JSBridge.sizeAndLocation(json) |
Authentication via Native Bridge
Mobile Aspects cannot use fetch() with session cookies like web Aspects do. Instead, authentication is delegated to the native app through the tokenApiDetails bridge.
How It Works
- The Aspect sends API call details (URL, method, headers) to the native app via
tokenApiDetails. - The native app performs the HTTP request on behalf of the Aspect, using its own session credentials.
- When the response is ready, the native app sets
window.dbk.tokenResponseand dispatches theapiToken:readyevent. - The Aspect listens for the event and uses the token to initialize the vendor SDK.
Sending Authentication Request
var apiCallDetails = {
method: 'GET',
headers: {
cookie: ''
},
url: 'https://<FI_DOMAIN>/feng-bff/live/v1/aspect/token?clientId=<YOUR_CLIENT_ID>'
};
var jsonString = JSON.stringify(apiCallDetails);
if (navigator.userAgent && navigator.userAgent.match(/(iPad|iPhone|iPod)/i)) {
window.webkit.messageHandlers.tokenApiDetails.postMessage(jsonString);
} else {
JSBridge.tokenApiDetails(jsonString);
}
Receiving the Token
async function getAuthToken() {
// Check if the token is already available
if (window.dbk && window.dbk.tokenResponse) {
return window.dbk.tokenResponse;
}
// Wait for the native app to deliver the token
return new Promise(function (resolve) {
var onReady = function () {
window.removeEventListener('apiToken:ready', onReady);
resolve(window.dbk.tokenResponse);
};
window.addEventListener('apiToken:ready', onReady);
});
}
Comparison with web authentication:
| Step | Web | Mobile |
|---|---|---|
| Send request | fetch(tokenUrl, { headers: { Cookie: document.cookie } }) | tokenApiDetails bridge → native app executes request |
| Receive response | fetch() Promise resolves with JSON | window.dbk.tokenResponse + apiToken:ready event |
| Session credentials | Session cookies sent via document.cookie | Handled by native app (cookies not available in WebView) |
Script Loading
Mobile Aspects do not have access to dbk.loadScript(). Instead, load external scripts manually using document.createElement('script'):
function loadScript(url) {
return new Promise(function (resolve, reject) {
var script = document.createElement('script');
script.src = url;
script.type = 'text/javascript';
script.onload = resolve;
script.onerror = function () {
reject(new Error('Failed to load script: ' + url));
};
document.body.appendChild(script);
});
}
loadScript('https://cdn.your-vendor.com/sdk/v3/widget.js')
.then(function () {
// Vendor SDK is now available
})
.catch(function (error) {
console.error(error.message);
});
var widgetDiv = document.createElement('div');
widgetDiv.id = 'vendor-widget';
var shadowRoot = widgetDiv.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = '<div></div>';
document.body.appendChild(widgetDiv);
The third-party SDK then mounts into this Shadow DOM container. Any styles injected by the vendor SDK are scoped to the shadow tree and do not leak into the surrounding document.
Window Size Negotiation
Mobile Aspects must communicate their layout requirements to the native app so it can size and position the WebView correctly. This concept does not exist on web — web Aspects rely on CSS for positioning.
The sizeAndLocation bridge accepts an aspectLocations array with bounding box coordinates relative to the screen:
function resizeWindow(aspectDetails) {
var jsonString = JSON.stringify(aspectDetails);
if (navigator.userAgent && navigator.userAgent.match(/(iPad|iPhone|iPod)/i)) {
window.webkit.messageHandlers.sizeAndLocation.postMessage(jsonString);
} else {
JSBridge.sizeAndLocation(jsonString);
}
}
aspectLocations payload
| Field | Type | Description |
|---|---|---|
x | string | Left position of the Aspect area (pixels) |
y | string | Top position of the Aspect area (pixels) |
width | string | Width of the Aspect area (pixels) |
height | string | Height of the Aspect area (pixels) |
webWidth | string | Total WebView width (window.screen.width) |
webHeight | string | Total WebView height (window.screen.height) |
Condensed State
When the widget is collapsed (e.g. showing only a floating button), report the button's bounding box so the native app can minimize the WebView to just that area:
function condenseWindow() {
var rect = chatButton.getBoundingClientRect();
var aspectDetails = {
aspectLocations: [{
x: '' + rect.x,
y: '' + rect.y,
width: '' + (rect.width * 1.25),
height: '' + (rect.height * 1.25),
webWidth: '' + window.screen.width,
webHeight: '' + window.screen.height
}]
};
resizeWindow(aspectDetails);
}
Expanded State
When the widget is open (e.g. a full chat window), report full-screen dimensions so the native app expands the WebView to cover the app:
function expandWindow() {
var aspectDetails = {
aspectLocations: [{
x: '0',
y: '0',
width: '' + window.screen.width,
height: '' + window.screen.height,
webWidth: '' + window.screen.width,
webHeight: '' + window.screen.height
}]
};
resizeWindow(aspectDetails);
}
Widget State Tracking with MutationObserver
Because third-party SDKs manage their own UI state (open, closed, minimized), the Aspect needs to detect state changes and update the native app's window size accordingly. Use a MutationObserver to watch for attribute changes on the vendor widget's DOM element:
function observeWidgetState(widgetElement) {
var observer = new MutationObserver(function (mutationsList) {
for (var i = 0; i < mutationsList.length; i++) {
var mutation = mutationsList[i];
if (mutation.type === 'attributes') {
var isVisible = mutation.target.getAttribute(mutation.attributeName) === 'true';
if (isVisible) {
expandWindow();
} else {
condenseWindow();
}
}
}
});
observer.observe(widgetElement, {
attributes: true,
attributeFilter: ['data-visible'],
attributeOldValue: true
});
}
The specific attribute to observe (data-visible in the example above) depends on your vendor's SDK. Check the vendor's documentation for the attribute or event that signals open/close state.
Waiting for Async Elements
Third-party SDKs mount their UI asynchronously. Use a MutationObserver-based utility to wait for elements to appear inside the Shadow DOM before interacting with them:
function waitForElement(root, selector, timeout) {
timeout = timeout || 10000;
return new Promise(function (resolve, reject) {
var element = root.querySelector(selector);
if (element) {
resolve(element);
return;
}
var observer = new MutationObserver(function (mutations) {
for (var i = 0; i < mutations.length; i++) {
var addedNodes = mutations[i].addedNodes;
for (var j = 0; j < addedNodes.length; j++) {
var node = addedNodes[j];
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.matches && node.matches(selector)) {
observer.disconnect();
clearTimeout(timeoutId);
resolve(node);
return;
}
var found = root.querySelector(selector);
if (found) {
observer.disconnect();
clearTimeout(timeoutId);
resolve(found);
return;
}
}
}
}
});
observer.observe(root, { childList: true, subtree: true });
var timeoutId = setTimeout(function () {
observer.disconnect();
reject(new Error('Element not found: ' + selector));
}, timeout);
});
}
Usage:
var host = document.getElementById('vendor-widget');
waitForElement(host.shadowRoot, 'div.vendor-launcher')
.then(function (launcherElement) {
// Launcher is ready — set up state observation and initial layout
observeWidgetState(launcherElement);
condenseWindow();
})
.catch(function (error) {
console.error(error.message);
});
Security Considerations
The same security best practices apply as for web Aspects, with additional mobile-specific guidance:
- Bridge payloads should never contain secrets. The
tokenApiDetailsbridge sends request metadata (URL, method, headers) — the native app adds credentials. Do not include access tokens or passwords in the payload. - Validate all data received from the native bridge. Treat
window.dbk.tokenResponseas external input and validate its structure before use. - Optionally use Shadow DOM to isolate third-party widget styles and scripts from the WebView document.
Next Steps
- Mobile Examples — Complete working code examples
- Web Technical Reference — How Aspects work differently on web
- Aspects Overview — Categories and how to choose
- FAQ — Common questions